#ifndef __tenacitas_log_core__internal_threads__h__
#define __tenacitas_log_core__internal_threads__h__



/** @file */


/** @todo */

// ==> C++ Headers
#include <string>
#include <cstdint>
#include <utility>
#include <memory>
#include <thread>
#include <functional>
#include <list>
#include <mutex>
#include <algorithm>

// ==> 3rds Headers

// ==> Our Headers
#include <tenacitas.return_code.core/rc.h>


#include "../level.h"
#include "../thread_id.h"
#include "internal_logger.h"

// ==> Namespaces
using namespace std;
//namespace translator = tenacitas::translator::core;

// ==> Macro Constants

// ==> Macro Commands


/** ==> namespace of the organization */
namespace tenacitas {

/** ==> namespace of the project */
    namespace log {

        namespace core {

            // ==> Typedefs 

            // ==> Pre-Declarations

            // ==> Attributes 

            // ==> Functions

            // ==> Classes

            /** Type of the collection of the thread and the media where log
                messages will be recorded */
            template <typename supplier>
            struct internal_threads {

                /** */
                typedef typename supplier::media media;

                typedef core::internal_logger<supplier> internal_logger;

                static const class_id id = 14327538109756ULL;

                enum constants : constant_id {could_not_add_new_logger=100};

                /** */
                rc<internal_logger *> set_current(const level & p_level) noexcept;
                
            private:

                typedef std::list<internal_logger> collection;

                typedef typename collection::iterator iterator;

                typedef typename collection::const_iterator const_iterator;

                struct at_th_end {
                    at_th_end(internal_threads * p_threads) noexcept;
                    ~at_th_end();
                private:
                    internal_threads * m_threads;
                };

                struct equal_by_thread {
                    equal_by_thread(thread_id * p_th_id)
                        : m_th_id(p_th_id) {}
                    
                    bool operator()(const internal_logger & p_current) const
                        noexcept {
                        return (p_current.get_thread_id() == *m_th_id);
                    }
                private:
                    thread_id * m_th_id;
                };

                struct is_unused {
                    bool operator()(const internal_logger & p_current) const
                        noexcept {
                        return p_current.is_unused();
                    }
                };
                
                /** */
                void make_unused(const thread_id & p_thread_id) noexcept;

                /** */
                std::mutex m_mutex;
                    
                collection m_coll;
            };

        }
    }
}
// =============================================================================
// I M P L E M E N T A T I O N
// =============================================================================

using namespace tenacitas::log::core;

// -----------------------------------------------------------------------------
template <typename supplier>
const class_id
internal_threads<supplier>::
id;

// -----------------------------------------------------------------------------
template <typename supplier>
rc<typename internal_threads<supplier>::internal_logger *>
internal_threads<supplier>::
set_current(const level & p_level) noexcept {
    
    internal_logger * _ret = nullptr;
    
    thread_id _thread_id;

    rc<internal_logger *> _rc;
        
    iterator _ite = std::find_if(m_coll.begin(), m_coll.end(),
                                 equal_by_thread (&_thread_id));

    // could not find @p _thread_id in the collection of threads
    if (_ite == m_coll.end()) {

        // would there be an unused one?
        _ite = std::find_if(m_coll.begin(), m_coll.end(), is_unused());

        if (_ite == m_coll.end()) {
            // no, so we have to create a new entry

            // I can lock only here, because the thread _th_id can not be
            // modified in another thread
            std::lock_guard<std::mutex> lock(m_mutex);

            try {                
                m_coll.push_back(internal_logger(p_level, _thread_id));
            } catch (std::exception & _ex) {
        
                _rc = rc<internal_logger *>(id,
                                            constants::could_not_add_new_logger,
                                            __FILE__, __LINE__, _ex.what());

                std::cerr << _rc << std::endl;
                return _rc;
            }

            _ret = & m_coll.back();

            thread_local at_th_end _ate(this);

        }
        else {
            // there is, so we reuse it
            try {
                *_ite = internal_logger(p_level, _thread_id);
            } catch (rc<> & _ex) {
                _rc = rc<internal_logger *> (_ex);
                std::cerr << _rc << std::endl;
                return _rc;
            }
            _ret = & (*_ite);
        }
    }
    else {
        // found a @p current associated to this _thread_id
        _ret = & (*_ite);
        _ret->set_level(p_level);
    }


    return rc<internal_logger *>(std::move(_ret));
}

// // -----------------------------------------------------------------------------
// template <typename supplier>
// typename internal_threads<supplier>::iterator
// internal_threads<supplier>::
// find_unused() {
//     iterator l_ite = m_coll.begin();
//     iterator l_end = m_coll.end();

//     for(;l_ite != l_end;++l_ite) {
//         if (l_ite->is_unused()){
//             break;
//         }
//     }
//     return l_ite;
// }

// -----------------------------------------------------------------------------
template <typename supplier>
void
internal_threads<supplier>::
make_unused(const thread_id & p_thread_id) noexcept {
    std::lock_guard<std::mutex> lock(m_mutex);
    iterator l_ite = std::find_if(m_coll.begin(), m_coll.end(),
                                  equal_by_thread(&p_thread_id)); 
    if (l_ite != m_coll.end()) {
        l_ite->set_unused();
    }
}


// -----------------------------------------------------------------------------
template <typename supplier>
internal_threads<supplier>::at_th_end::
at_th_end(internal_threads * p_threads) noexcept
    : m_threads(p_threads) {
    std::cerr << std::endl
              << std::hash<std::thread::id>()(std::this_thread::get_id())
              << " starting..." << std::endl;
}

// -----------------------------------------------------------------------------
template <typename supplier>
internal_threads<supplier>::at_th_end::
~at_th_end() {
    std::cerr << std::endl
              << std::hash<std::thread::id>()(std::this_thread::get_id())
              << " dying..." << std::endl;
}



#endif
